/*
* Copyright 1993-2010 NVIDIA Corporation.  All rights reserved.
*
* NVIDIA Corporation and its licensors retain all intellectual property and
* proprietary rights in and to this software and related documentation.
* Any use, reproduction, disclosure, or distribution of this software
* and related documentation without an express license agreement from
* NVIDIA Corporation is strictly prohibited.
*
* Please refer to the applicable NVIDIA end user license agreement (EULA)
* associated with this source code for terms and conditions that govern
* your use of this NVIDIA software.
*
*/


#ifndef __GPU_ANIM_H__
#define __GPU_ANIM_H__

#include "gl_helper.h"

#include "cuda.h"
#include "cuda_runtime.h"
#include "cuda_runtime_api.h"
#include "cuda_gl_interop.h"
#include <iostream>
#include <curand.h>


PFNGLBINDBUFFERARBPROC    glBindBuffer     = NULL;
PFNGLDELETEBUFFERSARBPROC glDeleteBuffers  = NULL;
PFNGLGENBUFFERSARBPROC    glGenBuffers     = NULL;
PFNGLBUFFERDATAARBPROC    glBufferData     = NULL;


struct GPUAnimBitmap {
	GLuint  bufferObj;
	cudaGraphicsResource *resource;
	int     width, height;
	int		*curPos, *nextPos, *exitPos;
	int		*env;
	void    *dataBlock;
	bool	*fini;
	float	*runningTime;
	int		*runningLoop;

	void (*fAnim)(uchar4*,void*,GPUAnimBitmap &); 
	void (*animExit)(void*);
	void (*clickDrag)(void*,int,int,int,int);
	void (*dpAction)(int,int,int*,int*); //decision point action
	int  dragStartX, dragStartY;
	bool animated;

	curandGenerator_t gen;
	unsigned int *rands;
	int randAmount;

	GPUAnimBitmap( int w, int h, int randno, void *d = NULL ) {
		width = w;
		height = h;
		dataBlock = d;
		clickDrag = NULL;
		randAmount = randno;

		// first, find a CUDA device and set it to graphic interop
		cudaDeviceProp  prop;
		int dev;
		memset( &prop, 0, sizeof( cudaDeviceProp ) );
		prop.major = 1;
		prop.minor = 0;
		HANDLE_ERROR( cudaChooseDevice( &dev, &prop ) );
		cudaGLSetGLDevice( dev );

		// a bug in the Windows GLUT implementation prevents us from
		// passing zero arguments to glutInit()
		int c=1;
		char* dummy = " ";

		glutInit( &c, &dummy );
		glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGBA );
		glutInitWindowSize( width, height );
		glutCreateWindow( "bitmap" );

		glBindBuffer    = (PFNGLBINDBUFFERARBPROC)GET_PROC_ADDRESS("glBindBuffer");
		glDeleteBuffers = (PFNGLDELETEBUFFERSARBPROC)GET_PROC_ADDRESS("glDeleteBuffers");
		glGenBuffers    = (PFNGLGENBUFFERSARBPROC)GET_PROC_ADDRESS("glGenBuffers");
		glBufferData    = (PFNGLBUFFERDATAARBPROC)GET_PROC_ADDRESS("glBufferData");

		glGenBuffers( 1, &bufferObj );
		glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, bufferObj );
		glBufferData( GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 4,
			NULL, GL_DYNAMIC_DRAW_ARB );

		HANDLE_ERROR( cudaGraphicsGLRegisterBuffer( &resource, bufferObj, cudaGraphicsMapFlagsNone ) );
	}

	~GPUAnimBitmap() {
		free_resources();
	}

	void free_resources( void ) {
		HANDLE_ERROR( cudaGraphicsUnregisterResource( resource ) );

		glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, 0 );
		glDeleteBuffers( 1, &bufferObj );
	}

	long image_size( void ) const { return width * height * 4; }

	void click_drag( void (*f)(void*,int,int,int,int)) {
		clickDrag = f;
	}

	void anim_and_exit( 
		void (*f)(uchar4*,void*,GPUAnimBitmap &bitmap), 
		void(*e)(void*), 
		void(*dp)(int,int,int*,int*) ) 
	{
		GPUAnimBitmap**   bitmap = get_bitmap_ptr();
		*bitmap = this;
		fAnim = f;
		animExit = e;
		dpAction = dp;

		glutKeyboardFunc( Key );
		glutDisplayFunc( Draw );
		//if (clickDrag != NULL)
		glutMouseFunc( mouse_func );
		glutIdleFunc( idle_func );
		glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
		glutMainLoop();
	}

	// static method used for glut callbacks
	static GPUAnimBitmap** get_bitmap_ptr( void ) {
		static GPUAnimBitmap*   gBitmap;
		return &gBitmap;
	}

	// static method used for glut callbacks
	static void mouse_func( int button, int state,
		int mx, int my ) {
			if (button == GLUT_LEFT_BUTTON) {
				GPUAnimBitmap*   bitmap = *(get_bitmap_ptr());
				bitmap->animated = false;
				if (state == GLUT_UP){
					bitmap->dpAction(mx, my, bitmap->nextPos, bitmap->exitPos);
					//std::cout<<mx<<" "<<my<<std::endl;
					if(bitmap->animated == false){
						static int ticks = 1;
						uchar4*         devPtr;
						size_t  size;

						HANDLE_ERROR( cudaGraphicsMapResources( 1, &(bitmap->resource), NULL ) );
						HANDLE_ERROR( cudaGraphicsResourceGetMappedPointer( (void**)&devPtr, &size, bitmap->resource) );

						curandGenerate(bitmap->gen, bitmap->rands, bitmap->randAmount);
						bitmap->fAnim( devPtr, bitmap->dataBlock, *bitmap);
						HANDLE_ERROR( cudaGraphicsUnmapResources( 1, &(bitmap->resource), NULL ) );

						glutPostRedisplay();
					}
				}
				//if (state == GLUT_DOWN) {
				//	bitmap->dragStartX = mx;
				//	bitmap->dragStartY = my;
				//} else if (state == GLUT_UP) {
				//	bitmap->clickDrag( bitmap->dataBlock,
				//		bitmap->dragStartX,
				//		bitmap->dragStartY,
				//		mx, my );
				//}
			}
	}

	// static method used for glut callbacks
	static void idle_func( void ) {
		GPUAnimBitmap*  bitmap = *(get_bitmap_ptr());
		if(bitmap->animated == true){
			static int ticks = 1;
			uchar4*         devPtr;
			size_t  size;

			HANDLE_ERROR( cudaGraphicsMapResources( 1, &(bitmap->resource), NULL ) );
			HANDLE_ERROR( cudaGraphicsResourceGetMappedPointer( (void**)&devPtr, &size, bitmap->resource) );

			curandGenerate(bitmap->gen, bitmap->rands, bitmap->randAmount);
			bitmap->fAnim( devPtr, bitmap->dataBlock, *bitmap);
			HANDLE_ERROR( cudaGraphicsUnmapResources( 1, &(bitmap->resource), NULL ) );

			glutPostRedisplay();
		}
	}

	// static method used for glut callbacks
	static void Key(unsigned char key, int x, int y) {
		switch (key) {
		case 27:
			GPUAnimBitmap*   bitmap = *(get_bitmap_ptr());
			if (bitmap->animExit)
				bitmap->animExit( bitmap->dataBlock );
			bitmap->free_resources();
			exit(0);
		}
	}

	// static method used for glut callbacks
	static void Draw( void ) {
		GPUAnimBitmap*   bitmap = *(get_bitmap_ptr());
		glClearColor( 0.0, 0.0, 0.0, 1.0 );
		glClear( GL_COLOR_BUFFER_BIT );
		glDrawPixels( bitmap->width, bitmap->height, GL_RGBA,
			GL_UNSIGNED_BYTE, 0 );
		glutSwapBuffers();
	}
};


#endif  // __GPU_ANIM_H__

